import 'dart:async';
import 'dart:convert';

import 'package:efood_multivendor/controller/cart_controller.dart';
import 'package:efood_multivendor/controller/order_controller.dart';
import 'package:efood_multivendor/controller/splash_controller.dart';
import 'package:efood_multivendor/data/api/api_checker.dart';
import 'package:efood_multivendor/data/model/response/address_model.dart';
import 'package:efood_multivendor/data/model/response/place_details_model.dart';
import 'package:efood_multivendor/data/model/response/prediction_model.dart';
import 'package:efood_multivendor/data/model/response/response_model.dart';
import 'package:efood_multivendor/data/repository/location_repo.dart';
import 'package:efood_multivendor/helper/location_permission.dart';
import 'package:efood_multivendor/helper/route_helper.dart';
import 'package:efood_multivendor/helper/size_config.dart';
import 'package:efood_multivendor/util/app_constants.dart';
import 'package:efood_multivendor/util/images.dart';
import 'package:efood_multivendor/view/base/custom_snackbar.dart';

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:location/location.dart' as LOC;
import 'package:shared_preferences/shared_preferences.dart';

import '../util/dimensions.dart';
import '../view/base/custom_loader.dart';
import 'filter_controller.dart';

class LocationController extends GetxController implements GetxService {
  final LocationRepo locationRepo;

  LocationController({required this.locationRepo});

  bool _isServiceAble = true;
  Position _position = Position(
      longitude: 0,
      latitude: 0,
      timestamp: DateTime.now(),
      accuracy: 1,
      altitude: 1,
      heading: 1,
      speed: 1,
      speedAccuracy: 1,
      altitudeAccuracy: 0,
      headingAccuracy: 0);
  Position _pickPosition = Position(
      longitude: 0,
      latitude: 0,
      timestamp: DateTime.now(),
      accuracy: 1,
      altitude: 1,
      heading: 1,
      speed: 1,
      speedAccuracy: 1,
      altitudeAccuracy: 0,
      headingAccuracy: 0);
  bool _loading = false;
  String _address = '';
  String _pickAddress = '';
  List<Marker> _markers = <Marker>[];
  List<AddressModel> _addressList = [];
  List<AddressModel> _allAddressList = [];
  int _addressTypeIndex = 0;
  List<String> _addressTypeList = [
    'home',
    'office',
    'others',
    'current_location'
  ];
  bool _isLoading = false;
  bool _inZone = false;
  int _zoneID = 0;
  bool _buttonDisabled = true;
  bool _changeAddress = true;
  GoogleMapController? _mapController;
  List<PredictionModel> _predictionList = [];
  bool _updateAddAddressData = true;
  String _currentAddress = "";
  bool _reloading = false;
  ResponseModel? _responseModel;
  CameraPosition? _cameraPosition;

  String get currentAddress => _currentAddress;

  List<PredictionModel> get predictionList => _predictionList;

  bool get isLoading => _isLoading;

  bool get loading => _loading;

  Position get position => _position;

  Position get pickPosition => _pickPosition;

  String get address => _address;

  String get pickAddress => _pickAddress;

  List<Marker> get markers => _markers;

  List<AddressModel> get addressList => _addressList;

  List<String> get addressTypeList => _addressTypeList;

  int get addressTypeIndex => _addressTypeIndex;

  bool get inZone => _inZone;

  int get zoneID => _zoneID;

  bool get buttonDisabled => _buttonDisabled;

  GoogleMapController? get mapController => _mapController;

  bool get reloading => Get.find<LocationController>().reloading;

  ResponseModel? get responseModel => _responseModel;

  CameraPosition? get cameraPosition => _cameraPosition;

  bool get isServiceAble => _isServiceAble;
  LOC.Location _location = LOC.Location();

  Future<AddressModel?> getCurrentLocation(bool fromAddress,
      {GoogleMapController? mapController,
      LatLng? defaultLatLng,
      bool notify = true,
      bool isShowToast = true,
      bool isNativePicMap = true}) async {
    Completer<AddressModel?> completer = Completer<AddressModel?>();
    LocationPermissionHelper.checkPermission(() async {
      AddressModel? _addressModel;
      _loading = true;
      if (notify) {
        update();
      }
      var ll = defaultLatLng;
      // ll = LatLng(9.020113, 38.752121);
      if (ll == null) {
        LOC.LocationData? _locationResult;
        try {
          _locationResult = await _location.getLocation();
          ll = LatLng(_locationResult.latitude!, _locationResult.longitude!);
        } on PlatformException catch (e) {
          if (e.code == 'PERMISSION_DENIED') {
            print('Permission denied');
          }
        } catch (e) {
          print(e);
        }
      }
      try {
        if (mapController != null) {
          mapController.animateCamera(CameraUpdate.newCameraPosition(
            CameraPosition(target: ll!, zoom: 17),
          ));
        }
        String _addressFromGeocode = await getAddressFromGeocode(ll!);
        fromAddress
            ? _address = _addressFromGeocode
            : _pickAddress = _addressFromGeocode;

        ResponseModel? _responseModel = await getZone(
            ll.latitude.toString(), ll.longitude.toString(), true);
        _buttonDisabled = !_responseModel!.isSuccess;
        _addressModel = AddressModel(
          latitude: ll.latitude.toString(),
          longitude: ll.longitude.toString(),
          addressType: 'current_location',
          zoneId: _responseModel.isSuccess
              ? int.parse(_responseModel.message ?? '0')
              : 0,
          address: _addressFromGeocode,
        );
        _loading = false;
        _currentAddress = _addressModel.address ?? '';
        if (_responseModel.isSuccess) {
          updateServiceAble(true);
          // reportLocation(_locationResult.latitude, _locationResult.longitude);
          if (!fromAddress) {
            Get.find<LocationController>().saveAddressAndNavigate(
                _addressModel, false, '/home', Get.currentRoute.isNotEmpty,
                dashboard: true);
            //  HomeScreen.loadData(true);
          }
          update();
        } else {
          updateServiceAble(false);
          if (Get.find<SplashController>()
                  .configModel
                  ?.deadzoneAvailableOrder ==
              1) {
            Get.back();
            // Get.to(ServiceUnavailableScreen());
          } else {
            if (isNativePicMap == true) {
              Get.back();
              Get.toNamed(RouteHelper.getPickMapRoute(
                  Get.currentRoute.isEmpty ? RouteHelper.accessLocation : "",
                  Get.currentRoute.isNotEmpty));
            }
            if (isShowToast) {
              showCustomSnackBar(
                  'service_not_available_in_current_location'.tr);
            }
          }
          update();
        }
      } catch (e) {
        Get.defaultDialog(
          title: "location_fetch_failed".tr,
          titlePadding:
              EdgeInsets.only(left: 30, right: 30, top: 30, bottom: 10),
          titleStyle: TextStyle(fontSize: 16),
          content: ListTile(
            onTap: () async {
              Get.find<SplashController>().setFirstOpen();
              LocationPermissionHelper.checkPermission(() async {
                Get.dialog(CustomLoader(), barrierDismissible: false);
                AddressModel? _address = await Get.find<LocationController>()
                    .getCurrentLocation(true);
                ResponseModel? _response = await Get.find<LocationController>()
                    .getZone(_address!.latitude!, _address.longitude!, false);
                if (_response?.isSuccess == true) {
                  updateServiceAble(true);
                  Get.find<LocationController>()
                      .saveAddressAndNavigateForBottomSheet(_address);
                  int timestamp = DateTime.now().millisecondsSinceEpoch;

                  final prefs = await SharedPreferences.getInstance();
                  prefs.setInt('LocationTimestamp', timestamp);
                } else {
                  updateServiceAble(false);
                  if (Get.find<SplashController>()
                          .configModel
                          ?.deadzoneAvailableOrder ==
                      1) {
                    // Get.to(ServiceUnavailableScreen());
                  } else {
                    Get.back();
                    showCustomSnackBar(
                        'service_not_available_in_current_location'.tr);
                  }
                }
                update();
              });
            },
            minLeadingWidth: 0,
            title: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                SvgPicture.asset(Images.location_gps, color: Colors.deepOrange),
                SizedBox(
                  width: 15,
                ),
                Center(
                  child: Text(
                    "user_current_location".tr,
                    style: TextStyle(
                        fontWeight: FontWeight.w600,
                        fontSize: Dimensions.fontSizeSmall * 1.3),
                  ),
                ),
              ],
            ),
            horizontalTitleGap: 2.8.w,
          ),
        );
      }
      update();
      completer.complete(_addressModel);
    });

    return completer.future;
  }

  void updateServiceAble(bool isServiceAble) {
    if (_isServiceAble == isServiceAble) return;
    _isServiceAble = isServiceAble;
    update();
  }

  Future<ResponseModel?> getZone(
      String lat, String long, bool markerLoad) async {
    if (markerLoad) {
      _loading = true;
    } else {
      _isLoading = true;
    }
    update();

    Response response = await locationRepo.getZone(lat, long);
    if (response.statusCode == 200) {
      _inZone = true;
      _zoneID = response.body['zone_id'];
      _responseModel = ResponseModel(true, response.body['zone_id'].toString());
    } else {
      _inZone = false;
      _responseModel = ResponseModel(false, response.statusText);
    }
    if (markerLoad) {
      _loading = false;
    } else {
      _isLoading = false;
    }
    update();
    return _responseModel;
  }

  Future<void> updatePosition(CameraPosition position, bool fromAddress) async {
    // print(
    //     '\x1B[33m[update location]\x1B[0m ${position.target.latitude} + ${position.target.longitude}');
    if (_updateAddAddressData) {
      _loading = true;
      update();
      try {
        if (fromAddress) {
          _position = Position(
            latitude: position.target.latitude,
            longitude: position.target.longitude,
            timestamp: DateTime.now(),
            heading: 1,
            accuracy: 1,
            altitude: 1,
            speedAccuracy: 1,
            speed: 1,
            altitudeAccuracy: 0,
            headingAccuracy: 0,
          );
        } else {
          _pickPosition = Position(
            latitude: position.target.latitude,
            longitude: position.target.longitude,
            timestamp: DateTime.now(),
            heading: 1,
            accuracy: 1,
            altitude: 1,
            speedAccuracy: 1,
            speed: 1,
            altitudeAccuracy: 0,
            headingAccuracy: 0,
          );
        }
        ResponseModel? _responseModel = await getZone(
            position.target.latitude.toString(),
            position.target.longitude.toString(),
            true);
        _buttonDisabled = _responseModel?.isSuccess == false;
        if (_changeAddress) {
          String _addressFromGeocode = await getAddressFromGeocode(
              LatLng(position.target.latitude, position.target.longitude));
          fromAddress
              ? _address = _addressFromGeocode
              : _pickAddress = _addressFromGeocode;
          // print(
          //     '\x1B[33m[pick address on update position]\x1B[0m $_pickAddress');
        } else {
          _changeAddress = true;
        }
      } catch (e) {}
      _loading = false;
      update();
    } else {
      _updateAddAddressData = true;
    }
  }

  Future<ResponseModel> deleteUserAddressByID(int id, int index) async {
    Response response = await locationRepo.removeAddressByID(id);
    ResponseModel _responseModel;
    if (response.statusCode == 200) {
      _addressList.removeAt(index);
      _responseModel = ResponseModel(true, response.body['message']);
    } else {
      _responseModel = ResponseModel(false, response.statusText);
    }
    update();
    return _responseModel;
  }

  Future<void> getAddressList() async {
    Response response = await locationRepo.getAllAddress();
    if (response.statusCode == 200) {
      _addressList = [];
      _allAddressList = [];
      response.body.forEach((address) {
        _addressList.add(AddressModel.fromJson(address));
        _allAddressList.add(AddressModel.fromJson(address));
      });
    } else {
      ApiChecker.checkApi(response);
    }
    update();
  }

  Future<void> reportLocation(double lat, double long) async {
    Response response = await locationRepo.reportLocation(lat, long);
    if (response == 200) {
    } else {}
  }

  void filterAddresses(String queryText) {
    if (_addressList.isNotEmpty) {
      _addressList = [];
      if (queryText.isEmpty) {
        _addressList.addAll(_allAddressList);
      } else {
        _allAddressList.forEach((address) {
          if (address.address!
              .toLowerCase()
              .contains(queryText.toLowerCase())) {
            _addressList.add(address);
          }
        });
      }
      update();
    }
  }

  Future<ResponseModel> addAddress(
      AddressModel addressModel, bool fromCheckout) async {
    _isLoading = true;
    update();
    Response response = await locationRepo.addAddress(addressModel);
    _isLoading = false;
    ResponseModel responseModel;
    // print(response.statusCode);
    // print(response.statusText);
    if (response.statusCode == 200) {
      if (fromCheckout && getUserAddress()?.zoneId != addressModel.zoneId) {
        responseModel = ResponseModel(
            false, 'your_selected_location_is_from_different_zone'.tr);
      } else {
        getAddressList();
        String message = response.body["message"];
        responseModel = ResponseModel(true, message);
      }
    } else {
      responseModel = ResponseModel(
          false,
          response.statusText == 'Out of coverage!'
              ? 'service_not_available_in_this_area'.tr
              : response.statusText);
    }
    update();
    return responseModel;
  }

  Future<ResponseModel> updateAddress(
      AddressModel addressModel, int addressId) async {
    _isLoading = true;
    update();
    Response response =
        await locationRepo.updateAddress(addressModel, addressId);
    ResponseModel responseModel;
    if (response.statusCode == 200) {
      getAddressList();
      responseModel = ResponseModel(true, response.body["message"]);
    } else {
      responseModel = ResponseModel(false, response.statusText);
    }
    _isLoading = false;
    update();
    return responseModel;
  }

  Future<bool> saveUserAddress(AddressModel address) async {
    String userAddress = jsonEncode(address.toJson());
    return await locationRepo.saveUserAddress(userAddress, address.zoneId!);
  }

  AddressModel? getUserAddress() {
    AddressModel? _addressModel;
    try {
      _addressModel =
          AddressModel.fromJson(jsonDecode(locationRepo.getUserAddress()));
    } catch (e) {}
    return _addressModel;
  }

  void setAddressTypeIndex(int index) {
    _addressTypeIndex = index;
    update();
  }

  void saveAddressAndNavigateForBottomSheet(
    AddressModel address,
  ) {
    _setZoneDataForBottomsheet(address);

    Get.find<FilterController>().setPrice(
        Get.find<SplashController>()
            .configModel
            ?.minimumProductPrice
            ?.toDouble(),
        Get.find<SplashController>()
            .configModel
            ?.maximumProductPrice
            ?.toDouble());

    locationRepo.saveLocation(
        double.parse(address.latitude!), double.parse(address.longitude!));
  }

  void saveAddressAndNavigate(
      AddressModel address, bool fromSignUp, String route, bool canRoute,
      {bool homeBottomsheet = false,
      bool dashboard = false,
      bool fromCheckout = false}) async {
    if (!dashboard) {
      await getZone(address.latitude!, address.longitude!, false);
    }
    if (Get.find<CartController>().cartList.length > 0) {
      _setZoneData(address, fromSignUp, route, canRoute,
          fromCheckout: route == 'checkout', dashboard: dashboard);
    } else {
      if (homeBottomsheet) {
        _setZoneDataForBottomsheet(address);
      } else {
        _setZoneData(address, fromSignUp, route, canRoute,
            fromCheckout: route == 'checkout', dashboard: dashboard);
      }
    }
    locationRepo.saveLocation(
        double.parse(address.latitude!), double.parse(address.longitude!));
  }

  void _setZoneData(
      AddressModel address, bool fromSignUp, String route, bool canRoute,
      {bool fromCheckout = false, bool dashboard = false}) {
    if (_responseModel?.isSuccess == true) {
      Get.find<CartController>().clearCartList();
      address.zoneId = int.parse(_responseModel?.message ?? '0');
      autoNavigate(address, route, canRoute, dashboard: dashboard);
    } else {
      Get.back();
      showCustomSnackBar(_responseModel?.message);
    }
  }

  void _setZoneDataForBottomsheet(AddressModel address) {
    Get.find<LocationController>()
        .getZone(address.latitude!, address.longitude!, false)
        .then((response) async {
      if (response?.isSuccess == true) {
        Get.find<CartController>().clearCartList();
        address.zoneId = int.parse(response?.message ?? "0");
        Get.back();
        await Get.find<LocationController>().saveUserAddress(address);
        Get.back();
        _reloading = true;
      } else {
        Get.back();
        showCustomSnackBar(response?.message);
      }
    });
  }

  void autoNavigate(AddressModel address, String route, bool canRoute,
      {bool dashboard = false}) async {
    if (Get.find<LocationController>().getUserAddress() != null &&
        Get.find<LocationController>().getUserAddress()?.zoneId !=
            address.zoneId) {
      FirebaseMessaging.instance.unsubscribeFromTopic(
          'zone_${Get.find<LocationController>().getUserAddress()?.zoneId}_customer');
      FirebaseMessaging.instance
          .subscribeToTopic('zone_${address.zoneId}_customer');
    } else {
      FirebaseMessaging.instance
          .subscribeToTopic('zone_${address.zoneId}_customer');
    }

    await Get.find<LocationController>().saveUserAddress(address);
    // HomeScreen.loadData(false);
    // HomeScreen.loadData(false);
    // if (!dashboard) {
    //   HomeScreen.loadData(false);
    // }

    if (route != 'checkout') {
      Get.find<OrderController>().clearPrevData();
    }
    if (dashboard) {
      return;
    }
    if (route == 'checkout') {
      Get.back();
    } else if (route.isNotEmpty && canRoute) {
      Get.offAllNamed(route);
    } else {
      Get.offAllNamed(RouteHelper.getInitialRoute('others'));
    }
  }

  void setLocation(
      String placeID, String address, GoogleMapController mapController) async {
    _loading = true;
    update();

    LatLng _latLng = LatLng(0, 0);
    Response response = await locationRepo.getPlaceDetails(placeID);
    if (response.statusCode == 200) {
      PlaceDetailsModel _placeDetails =
          PlaceDetailsModel.fromJson(response.body);
      if (_placeDetails.status == 'OK') {
        _latLng = LatLng(_placeDetails.result!.geometry!.location!.lat!,
            _placeDetails.result!.geometry!.location!.lng!);
      }
    }

    _pickPosition = Position(
      latitude: _latLng.latitude,
      longitude: _latLng.longitude,
      timestamp: DateTime.now(),
      accuracy: 1,
      altitude: 1,
      heading: 1,
      speed: 1,
      speedAccuracy: 1,
      altitudeAccuracy: 1,
      headingAccuracy: 1,
    );

    _pickAddress = address;
    _changeAddress = false;
    // print('\x1B[33m[pick address on set location]\x1B[0m $_pickAddress');

    if (mapController != null) {
      mapController.animateCamera(CameraUpdate.newCameraPosition(
          CameraPosition(target: _latLng, zoom: 17)));
    }
    _loading = false;
    update();
  }

  void disableButton() {
    _buttonDisabled = true;
    _inZone = true;
    update();
  }

  void setAddAddressData() {
    _position = _pickPosition;
    _address = _pickAddress;
    _updateAddAddressData = false;
    // print('\x1B[33m[pick address on set address data]\x1B[0m $_pickAddress');
    update();
  }

  void setUpdateAddress(AddressModel address) {
    _position = Position(
      latitude: double.parse(address.latitude!),
      longitude: double.parse(address.longitude!),
      timestamp: DateTime.now(),
      altitude: 1,
      heading: 1,
      speed: 1,
      speedAccuracy: 1,
      floor: 1,
      accuracy: 1,
      altitudeAccuracy: 1,
      headingAccuracy: 1,
    );
    _address = address.address ?? '';
    _addressTypeIndex = _addressTypeList.indexOf(address.addressType!);
  }

  void setPickData() {
    _pickPosition = _position;
    _pickAddress = _address;
    // print(
    //     '\x1B[33m[pick address on set pick data method]\x1B[0m $_pickAddress');
  }

  void setMapController(GoogleMapController mapController) {
    _mapController = mapController;
  }

  Future<String> getAddressFromGeocode(LatLng latLng) async {
    // print(
    //     '\x1B[33m[Get address from geocode]\x1B[0m ${latLng.latitude} + ${latLng.longitude}');
    Response response = await locationRepo.getAddressFromGeocode(latLng);
    String _address = 'Unknown Location Found';
    Placemark? placemark;
    if (response.statusCode == 200 && response.body['status'] == 'OK') {
      String formattedAddress =
          response.body['results'][0]['formatted_address'].toString();
      try {
        placemark = await getAddressFromLatLang(latLng);
      } catch (e) {
        debugPrint(e.toString());
      }

      if (placemark?.isoCountryCode == 'ET') {
        if (placemark?.street?.isNotEmpty == true ||
            placemark?.name != null && placemark?.name?.isNotEmpty == true) {
          _address = placemark?.street?.isNotEmpty == true
              ? placemark!.street!
              : placemark!.name!;
          if (placemark.subLocality != null &&
              placemark.subLocality?.isNotEmpty == true &&
              placemark.subLocality != placemark.street) {
            _address += ', ' + placemark.subLocality!;
          }
          _address += ', ' + formattedAddress;
        } else if (placemark?.subLocality != null &&
            placemark?.subLocality?.isNotEmpty == true) {
          _address = placemark!.subLocality! + ', ' + formattedAddress;
        } else {
          _address = formattedAddress;
        }
      } else {
        _address = formattedAddress;
      }
    } else {
      showCustomSnackBar(response.body['error_message'] ?? response.bodyString);
    }

    return _address;
  }

  Future<Placemark> getAddressFromLatLang(LatLng latLng) async {
    // print(
    //     '\x1B[33m[getAddressFromLatLang]\x1B[0m ${latLng.latitude} +  ${latLng.longitude}');
    try {
      List<Placemark> placemarks =
          await placemarkFromCoordinates(latLng.latitude, latLng.longitude);
      debugPrint(placemarks.toList().toString());

      Placemark newPlaceMark = new Placemark();
      for (Placemark placemark in placemarks) {
        if (_isValidStreet(
            placemark.street == '' ? placemark.name! : placemark.street!)) {
          newPlaceMark = placemark;
          return newPlaceMark;
        }
      }
      for (Placemark placemark in placemarks) {
        if (placemark.subLocality != null) {
          newPlaceMark = placemark;
          return newPlaceMark;
        }
      }
    } catch (e) {
      debugPrint(e.toString());
    }

    return Placemark();
  }

  bool _isValidStreet(String street) {
    // Check if the street name is "Unnamed Road"

    if (street == 'Unnamed Road' || street.isEmpty) {
      //  print('\x1B[33m[INVALID STREET UNNAMED ROAD]\x1B[0m $street');
      return false;
    }

    // Check if the street name contains any numbers
    if (RegExp(r'\d').hasMatch(street)) {
      //print('\x1B[33m[INVALID STREET HAVE DECIMAL]\x1B[0m $street');
      return false;
    }

    return true;
  }

  String inputs(TextEditingController inputAddress) {
    if (Get.find<LocationController>().getUserAddress()!.addressType ==
        "home") {
      inputAddress.text = AppConstants.homeLocation;
    } else if (Get.find<LocationController>().getUserAddress()!.addressType ==
        "office") {
      inputAddress.text = AppConstants.workLocation;
    } else {
      inputAddress.text = AppConstants.currentLocation;
    }
    update();
    return inputAddress.text;
  }

  Future<List<PredictionModel>> searchLocation(
      BuildContext context, String text) async {
    if (text.isNotEmpty) {
      Response response = await locationRepo.searchLocation(text);
      if (response.statusCode == 200 && response.body['status'] == 'OK') {
        _predictionList = [];
        response.body['predictions'].forEach((prediction) =>
            _predictionList.add(PredictionModel.fromJson(prediction)));
      } else {}
    }
    return _predictionList;
  }

  void setPlaceMark(String address) {
    _address = address;
  }

  void setCameraPosition(CameraPosition cameraPosition) {
    _cameraPosition = cameraPosition;
    update();
  }
}
